React-redux dispatch function with parameters from child component is undefined - javascript

I have an action
export const saveNewTask = (taskName, taskDescription) => async dispatch => {
console.log(taskName, taskDescription);
const res = await axios.post('/api/add-new-task', {
taskName,
taskDescription
});
console.log(res);
dispatch({ type: SAVE_TASK, payload: res.data });
};
a parent container that contains a child container which is a Formik form and should allow the child component to save the Formik form contents to the db via the above mentioned action.
import { saveNewTask } from '../actions/index';
class ClientDashboard extends React.Component {
render() {
return <NewTask saveNewTask={saveNewTask()} />;
}
}
export default connect(null, { saveNewTask })(Container);
NewTask.js child component that does not know anything about Redux:
function NewTask(props) {
const formik = useFormik({
initialValues: {
taskName: '',
taskDescription: ''
},
onSubmit: values => {
// alert(JSON.stringify(values, null, 2));
props.saveNewTask(values.taskName, values.taskDescription);
}
});
...
}
but the action never gets the parameters that I am trying to pass through the Formik component (the commented alert line displays both values perfectly) and says both taskName and taskDescription are undefined. Also the dispatch in the last line of the action says
Uncaught (in promise) TypeError: dispatch is not a function at Object.saveNewTask
I think this is more of a syntax error than anything else but any help would be greatly appreciated.

The problem is in this line:
return <NewTask saveNewTask={saveNewTask()} />;
You need to pass a prop for calling the action, not the result of the function call.
You need to call your prop, not your action creator method.
So, change that line to:
return <NewTask saveNewTask={this.props.saveNewTask} />;

Related

Error passing function to child component react (Uncaught (in promise) TypeError: result is not a function)

I want to pass the result from an api call made on the child component to parent component so:
PARENT:
const Parent = () => {
function logFunction(resultFromAPI) {
console.log(resultFromAPI);
}
return(
<Child result={ logFunction } />
)
}
export default Parent
CHILD COMPONENT
const Child = ({result}) => {
const [values, setValues] = useState({
name: ''
})
const handleChange = (name) => (event) => {
setValues({ ...values, [name]: event.target.value });
};
const handleSubmit = async (e) => {
e.preventDefault();
const response = await createApi(values);
if (response.data.message) {
setValues({
name: "",
});
result(response.data); //Uncaught (in promise) TypeError: result is not a function
}
if (response.data.error) {
toast("error", response.data.message);
}
};
return(
<form onSubmit={handleSubmit}>
<Input
name='name'
value={name}
onChange={handleChange("name")}
/>
</form>
<button type='submit'>Submit</button>
)
}
export default Child
The form sends the "name" value on clic of the button to the function "handleSubmit" the function call an API.
The then, i want to call the "result" function from the child props. Put the result from the api on the function and log it on the parent.
But I got the error:
Uncaught (in promise) TypeError: result is not a function
The error message suggests that the "result" prop passed from the parent component to the child component is not a function.
Here are a few things you can try:
Verify the type of the "result" prop passed to the child component:
console.log(typeof result);
Make sure that the "logFunction" function is passed as a prop to the child component: return ( <Child result={logFunction} /> );
Make sure that you are calling the "result" function correctly:
result(response.data);
If you still encounter the error after trying these steps, please check the rest of your code and make sure that there are no other issues.

Child component doesn't rerender but parent component does rerender. How to make child component rerender?

Parent component does rerender upon receiving new props but its child component doesn't rerender. Child components only render for the first time and never rerender nor receive props from the redux store
I'm getting updated data from redux store in Parent component but not in the child components. Child components only receive data from redux store when they render for the first time
My Parent Component Home.js
Object seaFCLJSON look like this
const seaFCLJSON ={"rates": {"sort":"faster", "someOther": "someOtherValues"}};
when the redux store gets updated, seaFCLJSON looks like this
const seaFCLJSON ={"rates": {"sort":"cheaper","someOther": "someOtherValues"}};
class Home extends Component {
state = {
seaFCLJSON: {}
};
componentDidMount = () => {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
};
componentWillReceiveProps = nextProps => {
if (this.state.seaFCLJSON !== nextProps.seaFCLJSON) {
this.setState({ seaFCLJSON: nextProps.seaFCLJSON });
}
};
render() {
const { seaFCLJSON } = this.props;
return (
<>
{!isEmpty(seaFCLJSON) && seaFCLJSON.rates && seaFCLJSON.rates.fcl ? (
<FCLContainer fclJSON={seaFCLJSON} />
) : null} //it never rerenders upon getting updated data from redux store
<h5>{JSON.stringify(seaFCLJSON.rates && seaFCLJSON.rates.sort)}</h5> //it rerenders everytime upon getting updated data from redux store
</>
);
}
}
const mapStateToProps = state => {
return {
seaFCLJSON: state.route.seaFCLJSON
};
};
export default connect(
mapStateToProps,
actions
)(Home);
isEmpty.js
export const isEmpty = obj => {
return Object.entries(obj).length === 0 && obj.constructor === Object;
};
My Child Component FCLContainer.js
class FCLContainer extends Component {
state = {
seaFCLJSON: {}
};
componentDidMount = () => {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
};
componentWillReceiveProps = nextProps => {
console.log("outside state value: ", this.state.seaFCLJSON);
if (this.state.seaFCLJSON !== nextProps.seaFCLJSON) {
this.setState({ seaFCLJSON: nextProps.seaFCLJSON });
console.log("inside state value: ", this.state.seaFCLJSON);
}
};
render() {
const { seaFCLJSON } = this.state;
console.log("rendering .. parent props: ", this.props.fclJSON);
console.log("rendering .. redux store props: ", this.props.seaFCLJSON);
return (
<>
<div className="home-result-container">
<div>
<h5>This child component never rerenders :(</h5>
</div>
</div>
</>
);
}
}
const mapStateToProps = state => {
return {
seaFCLJSON: state.route.seaFCLJSON
};
};
export default connect(
mapStateToProps,
actions
)(FCLContainer);
I don't know whether there are problems in Parent component or problems in the child component. componentWillReceiveProps gets invoked in the parent component but not in the child component. Please ignore any missing semi-colon or braces because I have omitted some unnecessary codes.
Edit 1: I just duplicated value from props to state just for debugging purposes.
I will appreciate your help. Thank you.
Edit 2: I was directly changing an object in redux actions. That's the reason CWRP was not getting fired. It was the problem. For more check out my answer below.
componentWillReceiveProps will be deprecated in react 17, use componentDidUpdate instead, which is invoked immediately after updating occurs
Try something like this:
componentDidUpdate(prevProps, prevState) {
if (this.prevProps.seaFCLJSON !== this.props.seaFCLJSON) {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
}
};
At the first place it is absolutely meaningless to duplicate value from props to state, what is the meaning of it? Totally pointless, just keep it in props
About your issue - most probably this condition doesnt match, thats why child component doesnt trigger
!isEmpty(seaFCLJSON) && seaFCLJSON.rates && seaFCLJSON.rates.fcl
check it in debugger
As far as I can see, your problem is that you pass the following to your child component:
<FCLContainer fclJSON={seaFCLJSON} />
But you assume that you receive a prop called 'seaFCLJSON':
componentDidMount = () => {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
};
You should change your code to:
<FCLContainer seaFCLJSON={seaFCLJSON} />
Apart from that, as #Paul McLoughlin already mentioned, you should use the prop directly instead of adding it to your state.
I found the issue I was directly mutating the object in actions. I only knew state should not be directly mutated in class or inside reducer. I changed the actions where I was directly changing an object and then saving it in redux store via dispatch and, then I received the updated props in CWRP. This really took me a lot of times to figure out. This kind of issue is hard to find out at least for me. I guess I get this from https://github.com/uberVU/react-guide/issues/17
A lesson I learned: Never directly mutate an Object
I changed this
//FCL sort by faster
export const sortByFasterFCLJSON = () => async (dispatch, getState) => {
let seaFCLJSON = getState().route.seaFCLJSON;
if (!seaFCLJSON.rates) return;
seaFCLJSON.rates.fcl = _.orderBy(
seaFCLJSON.rates.fcl,
["transit_time"],
["asc"]
);
seaFCLJSON.rates.sort = "Faster"; //this is the main culprit
dispatch({ type: SET_SEA_FCL_JSON, payload: seaFCLJSON });
};
to this
//FCL sort by faster
export const sortByFasterFCLJSON = () => async (dispatch, getState) => {
let seaFCLJSON = getState().route.seaFCLJSON;
if (!seaFCLJSON.rates) return;
seaFCLJSON.rates.fcl = _.orderBy(
seaFCLJSON.rates.fcl,
["transit_time"],
["asc"]
);
// seaFCLJSON.rates.sort = "Faster"; //this was the main culprit, got lost
seaFCLJSON = {
...seaFCLJSON,
rates: { ...seaFCLJSON.rates, sort: "Faster" }
};
dispatch({ type: SET_SEA_FCL_JSON, payload: seaFCLJSON });
};
the power of not mutating data
Side note: Redux Troubleshooting

ReactJS calling function twice inside child component fails to set parent state twice

I'm having an issue where I want to save the data from a particular fieldset with the default values on componentDidMount().
The data saving happens in the parent component, after it is sent up from the child component. However, as React's setState() is asynchronous, it is only saving data from one of the fields. I have outlined a skeleton version of my problem below. Any ideas how I can fix this?
// Parent Component
class Form extends Component {
super(props);
this.manageData = this.manageData.bind(this);
this.state = {
formData: {}
}
}
manageData(data) {
var newObj = {
[data.name]: data.value
}
var currentState = this.state.formData;
var newState = Object.assign({}, currentState, newObj);
this.setState({
formData: newState, // This only sets ONE of the fields from ChildComponent because React delays the setting of state.
)};
render() {
return (
<ChildComponent formValidate={this.manageData} />
)
}
// Child Component
class ChildComponent extends Component {
componentDidMount() {
const fieldA = {
name: 'Phone Number',
value: '123456678'
},
fieldB = {
name: 'Email Address',
value: 'john#example.com'
}
this.props.formValidate(fieldA);
this.props.formValidate(fieldB)
}
render() {
/// Things happen here.
}
}
You're already answering you're own question. React handles state asynchronously and as such you need to make sure you use the current component's state when setState is invoked. Thankfully the team behind React is well-aware of this and have provided an overload for the setState method. I would modify your manageData call to the following:
manageData(data) {
this.setState(prevState => {
const nextState = Object.assign({}, prevState);
nextState.formData[data.name] = data.value;
return nextState;
});
}
This overload for the setState takes a function whose first parameter is the component's current state at the time that the setState method is invoked. Here is the link where they begin discussing this form of the setState method.
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Change manageData to this
manageData(data) {
const newObj = {
[data.name]: data.value
};
this.setState(prevState => ({
formData: {
...prevState.formData,
...newObj
}
}));
}

Not able to get values from Redux to props

I'm unable to get the props values from Redux, I checked in reducers, store, action JS files everything working fine. Even I can see the state printed correctly in console.log("MAPSTOSTATEPROPS", state) inside mapsStateToProps function below.
But when I give this.props.posts to access the values I'm not getting them. Not sure, where I'm doing wrong here. Can someone help me?
posts.js:
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
**console.log(this.props.posts); // gives empty array result**
}
render() {
return (
<div>
<h2>Posts</h2>
</div>
)
}
componentDidMount() {
console.log("DID mount", this.props);
}
}
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array
}
const mapsStateToprops = (state) => {
**console.log("MAPSTOSTATEPROPS", state)** // gives correct result from current state
return {
posts: state.items
}
};
export default connect(mapsStateToprops, {fetchPosts}) (Posts);
class Posts extends Component {
/* use did mount method because all will-methods
are will be deprecated in nearest future */
componentDidMount() {
/* on component mount you initiate async loading data to redux store */
this.props.fetchPosts();
/* right after async method you cannot expect to retrieve your data immediately so this.props.posts will return [] */
console.log(this.props.posts);
}
render() {
/* here you getting posts from props*/
const { posts } = this.props;
return (
<div>
<h2>Posts</h2>
/* here you render posts. After async method will be completed
and dispatch action with retrieved data that will cause rerender
of component with new props */
{posts.map(post => (<div>post.name</div>))}
</div>
)
}
}
Posts.propTypes = {
fetchPosts : PropTypes.func.isRequired,
posts : PropTypes.array
}
const mapsStateToprops = (state) => {
/* first call will log an empty array of items,
but on second render after loading items to store they will
appear in component props */
console.log("MAPSTOSTATEPROPS", state)
return {
posts : state.items || [];
}
This may be because of async nature of fetchPost() method.Try to log it inside render() and handle the async nature using promises.

Redux state not updating with javascript object

I have this container and component and the object yeast that Im trying to put in my store. However, its not working whenever I try and save it. The object looks like this
{ yeastType : value, temp: value}
Container.js
const mapDispatchToProps = (dispatch) => {
return {
handleYeastChange: (yeast) => {
dispatch(actions.updateYeast(yeast))
}
}
};
const RecipeYeastContainer = connect(
mapStateToProps,
mapDispatchToProps
)(RecipeYeast);
Component.js
updateYeastState = (updatedYeast) => {
this.props.handleYeastChange(updatedYeast)
};
I have no errors in the console or anything. When I open my redux dev tools its telling me the state has already been updated by the time the action is called. And thus only ever saving the first letter thats input into my field. It never persists it. Its really weird. Its also never showing up in the UI. I can type as much as I want and see the action firing and the state keeping the first letter but its not showing up in the input.
Whats weird is that when I change the code to pass in both yeastType and temp to the property function and construct the object in there it works. (See below)
This works: Container.js
const mapDispatchToProps = (dispatch) => {
return {
handleYeastChange: (yeastType, temp) => {
const yeast = {yeastType, temp}
dispatch(actions.updateYeast(yeast))
}
}
};
Component.js
updateYeastState = (updatedYeast) => {
this.props.handleYeastChange(updatedYeast.yeastType, updatedYeast.temp)
};
I cannot figure out why this is happening. I thought I could just pass the object all the way through and not have to reconstruct it.
Do you dispatch your action correctly? And in using redux, you are not updating the state of the component, you're updating the store and then the value in component is from your mapStateToProps function that get from the store. Say it you're updating your store with the object named yourReducer store. ex:
Container.js:
const mapStateToProps = (state) => ({
inputValue: state.yourReducer.value
})
const mapDispatchToProps = (dispatch) => ({
inputHandler: (e) => {
dispatch(yourAction({type: 'CHANGE_INPUT', value: e.target.value}))
// The store catches the action and pass to the reducer that can read the action's type
// in `yourReducer` will catch the 'CHANGE_INPUT' type and
// return a new value as same as the value that passed from the action
}
})
export default connect(mapStateToProps)(Component)
Component.js
export default class Component extends React.Component {
{ your custom function here ... }
render() {
const { inputValue, inputHandler } = this.props
return (
<div>
{/* The input will be the same as the inputValue changed */}
<input value={inputValue} onChange={inputHandler} />
</div>
)
}
}
For debugging redux you can try this redux-devtool.

Categories

Resources