0
votes

I have a component - 'App.tsx' that has a type definition for its props. The props include an array of todos and an action creator that will be injected by connect (react-redux). However I cannot wire the component 'App' in say 'index.tsx' without passing these required props. Is there an alternative to specifying these props as optional in the interface? If they are optional then I have to do null checks everywhere I access them.

I read through using redux with typescript among other documentation but found no answers.

index.tsx =>

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

The above code has errors because App component below expects the two params - 'todos' and 'fetchTodos'

App.tsx =>

interface AppProps {
    todos: Todo[];
    fetchTodos(): any;
}

class _App extends Component<AppProps> {

    onButtonClick = (): void => {
        this.props.fetchTodos();
    }
    renderList(): JSX.Element[] | undefined {
        return this.props.todos.map((todo: Todo) => {
            return (
                <div key={todo.id}>{todo.title}</div>
            );
        });
    }
    render(): JSX.Element {
        return (
            <>
                <button onClick={this.onButtonClick}>Fetch</button>
                {this.renderList()}
            </>
        );
    }
}

const mapStateToProps = ({ todos }: StoreState, ownProps: AppProps): { todos: Todo[]; } => {
    return {
        todos
    };
};

export const App = connect(mapStateToProps, { fetchTodos })(_App);

I would like to see if there is a way to not specify the two props as optional in the AppProps interface because they would absolutely be injected by connect.

3

3 Answers

0
votes

Just as any other TypeScript interface (or function parameters or class memembers), you can declare optional types using the ? flag:

interface AppProps {
    todos?: Todo[];
    fetchTodos()?: any;
}

You can read more about optional parameters and members in the docs.

Later edit: So you want the mapped props to be excluded?

Usually I declare my prop types as this, I created two prop interfaces (one for mapped props and one for props that I expected to be passed):

interface IOwnProps {
    fetchTodos(): any;
}

interface IPropsFromState {
    todos: Todo[];
}

type IProps = IOwnProps & IPropsFromState;

const mapStateToProps = (state: IRootState): IPropsFromState ({
   todos: state.todos  
});

class _App extends Component<IProps> {
 ...
}

export const App = connect(mapStateToProps)(_App);
0
votes

You have to understand how connect jugles the types.

Connect is the best explained when looking from the right to the left:

Your component accepts 2 props: todos, fetchTodos.

Then it looks at the mapDispatchToProps. Your implentation of this provides fetchTodos. The new prop list is: todos

It finally looks at mapStateToProps: it provides todos, but to execute the function, it requires an object containi g: todos, fetchTodos

Your final component contains these props: todos, fetchTodos

This is not what you want, you want none props.

You need to adjust your mapStateToProps function:

const mapStateToProps = ({ todos }: StoreState, ownProps: {}): { todos: Todo[]; } => { return { todos }; 

Or simply:

const mapStateToProps = ({ todos }: StoreState): { todos: Todo[]; } => { return { todos }; 
-1
votes

Here is how I solved the above:

interface ReduxInjectedProps{
   fetchTodos:typeof fetchTodos;
   todos:Todo[];
}  

class _App extends Component {
    get injected(){
       return this.props as ReduxInjectedProps;
    }
    onButtonClick = (): void => {
        this.injected.fetchTodos();
    }
    renderList(): JSX.Element[] | undefined {
        return this.injected.todos.map((todo: Todo) => {
            return (
                <div key={todo.id}>{todo.title}</div>
            );
        });
    }
    render(): JSX.Element {
        return (
            <>
                <button onClick={this.onButtonClick}>Fetch</button>
                {this.renderList()}
            </>
        );
    }
}

const mapStateToProps = ({ todos }: StoreState, ownProps: AppProps): { todos: Todo[]; } => {
    return {
        todos
    };
};

export const App = connect(mapStateToProps, { fetchTodos })(_App);