How to pass different function as props to the same component? - reactjs

"I am trying to add comment functionality in my react application and I want to call different functions depending on my user type"
"I have successfully applied the logic and also I am able to post comments and save it."
Here's my Code for CommentForm.js
import React, { useState, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addComment } from '../../actions/userdata';
const CommentForm = ({ userdataId, addComment }) => {
const [text, setText] = useState('');
return (
<Fragment>
{/* <div className='bg-primary p'>
<h3>Leave a Comment</h3>
</div> */}
<form
className='form my-1'
onSubmit={e => {
e.preventDefault();
addComment(userdataId, { text });
setText('');
}}
>
<textarea
name='text'
cols='3'
rows='3'
placeholder='Any Other Comments..'
value={text}
onChange={e => setText(e.target.value)}
required
/>
<input type='submit' className='btn btn-dark my-1' value='Submit' />
<small> {'<==='} Press this button to save Comments</small>
</form>
</Fragment>
);
};
CommentForm.propTypes = {
addComment: PropTypes.func.isRequired
};
export default connect(
null,
{ addComment }
)(CommentForm);
and I am using this in some other file as
<CommentForm userdataId={userdata._id}/>
The problem is that I have around 9 functions like addComment say addcommenti, addcommentii, addcommentiii,.... and so on in ../actions/userdata.
and Hence I'll have to create 9 files.
I already tried
<CommentForm userdataId={userdata._id} addComment={addCommenti}/>
but it's not working i.e it's making call to addComment and not to addCommenti .
Any ideas on how to proceed ?
Here's my addComment Function
//Add Comment
export const addComment = (userdataId, formData) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
try {
const res = await axios.post(
`/api/userdata/comment/${userdataId}`,
formData,
config
);
dispatch({
type: ADD_COMMENT,
payload: res.data
});
dispatch(setAlert('Comment Added', 'success'));
} catch (err) {
dispatch({
type: USERDATA_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};

You have an imported function and a prop with the same name, that seems to cause the problem here: You think that you access the prop, but actually your component calls the imported function.
Try to remove the object destructuring in your CommentForm Component function and call this code instead:
// ...
const CommentForm = (props) => { /* <<-- HERE */
const [text, setText] = useState('');
return (
<Fragment>
<form
className='form my-1'
onSubmit={e => {
e.preventDefault();
props.addComment(props.userdataId, { text }); /* <<-- HERE */
setText('');
}}
>
<textarea
name='text'
cols='3'
rows='3'
placeholder='Any Other Comments..'
value={text}
onChange={e => setText(e.target.value)}
required
/>
<input type='submit' className='btn btn-dark my-1' value='Submit' />
<small> {'<==='} Press this button to save Comments</small>
</form>
</Fragment>
);
};
// ...
This will explicitly use the function that you pass into the props and can not call the imported function by accident.
Though, you could also rename the prop to something like onAddComment so your prop and imported function always use different names (which makes the easier to understand, in my opinion)

So two things:
If you need to use 9 functions in your component, even if you only need one per user type, I would pass all 9 functions down and handle the logic for which to use there.
In this case you just want to add the remaining functions as you have already.
If you do want to override an action which is being injected using redux so that you can decide which function to pass in the parent component you can do it using the functional version of mapDispatchToProp; it has a second argument ownProps which you can use:
const mapDispatchToProps = (dispatch, ownProps) => ({
addComment: (text, userdataid) => dispatch(addComment(userdataid, { text }),
...ownProps,
});
because I have spread ownProps after declaring addComment, you can pass addComment from the parent and it will replace the one we declared above.

Related

react js myfn is not a function when called from a button

I've just started learning about react js and this is my first react js app. I'm using api to fetch the data. so far it works, but now I want to add a search keyword to the function that is acquired from a search bar component.
here's my code:
SearchBar.js
const SearchBar = ({ getUsers }) => {
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword"/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(document.querySelector('#query').value)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};
MasterUser.js
import { useState, useEffect } from "react";
import SearchBar from "./SearchBar";
const MasterUser = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsers();
}, []);
const getUsers = async (query='') => {
console.log('get users', query);
try {
let myurl = 'http://localhost:8080/users';
const response = await fetch(myurl);
const data = await response.json();
setUsers(data);
setIsLoading(false);
} catch (e) {
console.log(e.getMessage());
}
};
return (
<div>
<SearchBar onClick={getUsers}/>
</div>
);
};
when the app loads, the console log says get users <empty string> and it returns all the users as expected, but when I clicked on the search button (magnifyingGlass) it gives an error Uncaught TypeError: getUsers is not a function.
any help is appreciated..
<SearchBar onClick={getUsers}/>
You have named the prop onClick not getUsers. That's why you get that error.
Yeah, accessing dom element value using selectors (e.g. document.querySelector('#query').value) is also not typical react. Read about controlled form elements (save form element value in state).
Make your searchBar component more reactive like so
const SearchBar = ({ getUsers }) => {
const [searchValue,setSearchValue]=useState('');
return (
<div className="is-flex flex-align-items-center mb-3">
<input type="text" id="query" className="input search-input" placeholder="search keyword" value={searchValue} onChange={(e)=>setSearchValue(e.target.value)}/>
<Button className="search-btn ps-3 pe-3"
onClick={() => getUsers(searchValue)}>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</Button>
</div>
);
};

file upload using react-step-builder form with multiple pages

i am using react hook form to create a from with multiple pages
i am able to create it and it working with all filed except file-input-type how do i pass i file from another page and finaly pass it to api in the final page
i a have actualy 3 pages i have only added the 1st and final page (fist page has the file input filed and final page has the api to which it must be submitted)
form with file upload field
import { useForm } from "react-hook-form";
export default function Form(props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
<input style={styles.file} type="file" />
</div>
<input {...register("name", { required: true })}
name="husband"
value={props.getState("name")}
onChange={props.handleChange}
style={styles.input}
type="text"
placeholder="Name"
/>
<input onClick={handleSubmit(props.next)}
type="submit"
value="Next"
/>
form with submit button and api to which it must be uploaded
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const submitValue = (e) => {
// e.preventDefault();
props.state.patient = "true";
const data = props.state;
axios
.post("registration/", data)
.then(() => {
alert("updated data");
window.location = "/clogin";
})
.catch((error) => {
//var my_obj_str = JSON.stringify(error.response.data);
alert(JSON.stringify(error.response.data));
});
};
codesandbox
https://codesandbox.io/s/wizardly-worker-zicnr?file=/src/App.js
There are 2 options
Single form wraps all the steps
You can wrap the <Steps /> component with one form. Make the <Step />s components stateless that accepts onInputChange which will called upon input changes.
onInputChange call setValue to update the form's state.
When the form submitted, you have the file (among other inputs) so you can send it to the server.
import { useEffect } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, setValue } = useForm();
useEffect(() => {
register("myFile");
}, [register]);
const onInputChange = (e) => {
setValue(e.target.name, e.target.files[0]);
};
const onSubmit = (data) => {
alert(`Your file name: ${data.myFile.name}, size: ${data.myFile.size}`);
};
return (
<StepsProvider>
<form onSubmit={handleSubmit(onSubmit)}>
<MySteps onInputChange={onInputChange} />
</form>
</StepsProvider>
);
}
const MySteps = ({ onInputChange }) => {
const { next, prev } = useSteps();
return (
<Steps>
<div>
<h1>Step 1</h1>
<input type="file" name="myFile" onChange={onInputChange} />
<button onClick={next}>Next</button>
</div>
<div>
<h1>Step 2</h1>
<button>Submit</button>
</div>
</Steps>
);
};
https://codesandbox.io/s/gifted-wozniak-of14l?file=/src/App.js
Multiple forms in each step
If you want need to have a form inside each step, you can pass the step's data up to the parent when upon step's form submission. Still the parent has the form state so it can handle when all the steps completed
import { useRef } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const formState = useRef();
const onStepComplete = (data) => {
formState.current = {
...formState.current,
...data
};
};
const onComplete = (data) => {
onStepComplete(data);
const {
name,
myFile: [file]
} = formState.current;
alert(
`Your name: ${name} Your file name: ${file.name}, size: ${file.size}`
);
};
return (
<StepsProvider>
<MySteps onStepComplete={onStepComplete} onComplete={onComplete} />
</StepsProvider>
);
}
const MySteps = ({ onStepComplete, onComplete }) => {
return (
<Steps>
<Step1 onStepComplete={onStepComplete} />
<Step2 onComplete={onComplete} />
</Steps>
);
};
const Step1 = ({ onStepComplete }) => {
const { register, handleSubmit } = useForm();
const { next } = useSteps();
const onSubmit = (data) => {
onStepComplete(data);
next();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 1</h1>
<input type="file" {...register("myFile")} />
<button>Next</button>
</form>
);
};
const Step2 = ({ onComplete }) => {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
onComplete(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 2</h1>
<input type="text" {...register("name")} />
<button>Submit</button>
</form>
);
};
https://codesandbox.io/s/condescending-leaf-6gzoj?file=/src/App.js
Passing down the function and tracking changes at parent level is not a great idea. react-hook-form provides a form context option which allows you to do this independently. Such that errors, onChange are handled in each step separately. But when you need to submit the data you can get all of those in the parent component.
Refer to this documentation: https://react-hook-form.com/api/useformcontext/
Note: Many people make the mistake of placing the FormProvider inside the parent component. Remember that FormProvider should wrap the Parent component as well.

SOLVED : why Django admin list is not updating the edit from the frontend React js form?

i am trying to make a CRUD app in DRF-Reactjs by following Tania rascia's example
i have successfully implemented add, delete, list view. but i am trying to edit a specific row which is not updating in DRF backend. but the edited row is shown in the frontend list. why it is not updating in django admin list?
in DRF side views.py:
#api_view(['POST'])
def TodoUpdate(request, pk):
todo = Todo.objects.get(id=pk)
serializer = TodoSerializer(instance=todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
i am using cors header to interface between frontend to backend. here is the frontend code for edit:
App.js:
import React,{Fragment, useState,useEffect} from 'react'
import EditList from './components/EditList';
import axios from 'axios'
export default function App() {
const initialTodoSate = { id: null, title: "", body: "" };
const [todos, setTodos] = useState([]);
const [todoList, setTodolist] = useState(initialTodoSate);
const [editing, setEditing] = useState(false);
useEffect(()=>{
axios.get("http://localhost:8000/api/todo-list",{})
.then(res=>{
setTodos(res.data)
}).catch(err=>{
console.log(err)
})
},[])
const addTodoList = (todo) => {
axios
.post("http://localhost:8000/api/todo-create/",todo)
.then((res) => {
console.log(res.data);
todo.id = todos.length + 1;
setTodos([todo, ...todos]);
})
.catch((err) => {
console.log(err);
});
};
const deleteTodo = (id) => {
setEditing(false);
axios.delete(`http://localhost:8000/api/todo-delete/${id}/`)
.then(res=>{
setTodos(todos.filter((todo) => todo.id !== id));
}).catch(err=>{
console.log(err)
})
};
const updateTodo = ( id,updatedTodo) => {
axios
.post(`http://localhost:8000/api/todo-update/${id}/`, id)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
setEditing(false);
setTodos(todos.map((todo) => (todo.id === id ? updatedTodo : todo)));
};
const editRow = (todo) => {
setEditing(true);
setTodolist({
id: todo.id,
title: todo.title,
description: todo.description,
});
};
return (
<div className="container">
<h1>Django-based Todo with React Hooks</h1>
{editing ? (
<Fragment>
<h3>Edit Task</h3>
<EditList
editing={editing}
setEditing={setEditing}
todoList={todoList}
updateTodo={updateTodo}
/>
</Fragment>
) : (
<Fragment>
<CreateTodo addTodoList={addTodoList} />
<hr />
</Fragment>
)}
<div className="flex-row">
<div className="flex-large">
<TodoList todos={todos} editRow={editRow} deleteTodo={deleteTodo} />
</div>
</div>
</div>
);
}
and EditList.js:
import React, { useState,useEffect } from "react";
export default function EditList({ todoList, setEditing, updateTodo }) {
const [todo, setTodo] = useState([todoList]);
useEffect(() => {
setTodo(todoList);
}, [todoList]);
const handleChange = (e) => {
const { name, value } = e.target;
setTodo({ ...todo, [name]: value });
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
updateTodo(todo.id, todo);
}}
>
<label>Title:</label>
<br />
<input
type="text"
name="title"
value={todo.title}
onChange={handleChange}
/>
<br />
<label>Description:</label>
<br />
<input
type="text"
name="body"
value={todo.body}
onChange={handleChange}
/>
<br />
<button>Update Task</button>
<button onClick={() => setEditing(false)} className="button muted-button">
Cancel
</button>
</form>
);
}
when i try to edit one row with title and body, it is edited and after pressing the update button, the updated row included in the list. but the problem is when i look into the django admin it has not been updated and when i check the development tools, i found an error:
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
at input
at form
at EditList (http://localhost:3000/static/js/main.chunk.js:511:3)
at div
at App (http://localhost:3000/static/js/main.chunk.js:70:83)
console. # vendors~main.chunk.js:31671
where am i having the mistake?
can anyone help me please? please let me know if you need any additional codes or information.
Trying to update something should be done in a put request, not a post request. This is a REST API convention, but a discrepancy may have some consequence down the line.
In this case, the error in your development tools is telling you that one of your components has an onChange/onSubmit etc property that is changing over the course of one mount from null to a function. This is not what's causing your issue, but I suspect it can be fixed by declaring the code in a handleSubmit function and then putting that into your onSubmit.
I think the error that's actually causing your problem is that the updatedTodo is not being sent to the backend. All that is being sent is the id (second parameter of axios.post). So if you pause the backend during execution, you would see that request.data = the id only, when it should be TodoSerializer's readable fields.
PS:
You can add a "debugger;" statement in the code after the updateToDo async request error to see what the error actually is (read more on the development tools debugging - browser dependent).
Don't abuse fragments - in this case, it would make for a more accessibility-friendly experience if you use divs in most of these components. Wouldn't it make more sense if the heading of some content was grouped with the content? https://developers.google.com/web/fundamentals/accessibility/focus/dom-order-matters

Setting state of a 'Loading' boolean in react doesn't update

I have a page which allows a user to submit a url from which data is scraped. The user is subsequently presented with the filtered data.
Because the scraping takes some time I would like to implement a loader. While the loader class will (hopefully) be relatively straight forward, it's the state for loading which I'm having issues with. The state itself is never updated. Although other state values are such as setFilters.
Body.js
const [searchState, setSearchState] = useState({
searchCriteria: "https://en.wikipedia.org/wiki/2020_Central_Vietnam_floods",
headers:[],
references: []
});
const [filterState, setFilters] = useState({
languageFilter: ""
});
const [loadingState, setLoadingState] = useState({
loading: false
});
The above are all passed into Search with a context
<>
<SearchContext.Provider value={{searchState, setSearchState,filterState, setFilters, loadingState, setLoadingState}} >
<Search />
<DonateButton />
<WikiHeaderGroup />
</SearchContext.Provider>
</>
And then I have a handleSubmit inside the Search component.
Search.js
import React, {useContext} from "react";
import {SearchContext} from "../../contexts/SearchContext"
import "../../App.css"
export function Search (){
const {searchState, setSearchState, filterState, setFilters, loadingState, setLoadingState} = useContext(SearchContext);
const handleSubmit = (event) => {
setFilters({languageFilter:""})
setLoadingState({loading:true})
console.log("Loading State : " + loadingState.loading)
event.preventDefault();
event.persist(); //persists the event object into the function
const fetchReferences = async () => {
fetch('http://127.0.0.1:8080/search/', {
method: 'POST',
body: JSON.stringify({
url: searchState.searchCriteria
}),
headers: {"Content-type": "application/json; charset=UTF-8"}
}).then(response => {
console.log(response)
return response.json()
}).then(json => {
console.log(json)
setSearchState({
headers:json.headers,
references:json.references
})
setLoadingState({loading:false})
console.log("Loading State : " + loadingState.loading)
});}
fetchReferences();
}
return (
<div className="search container">
<div className="input-group input-group-sm mb-3 center">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">Wikipedia URL:</span>
</div>
<form onSubmit={(event) => handleSubmit(event)}>
<input
type="text"
id="searchBox"
className="form-control center"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-sm"
value={searchState.searchCriteria}
onChange={(event) => setSearchState({searchCriteria:event.target.value, resultId:0})}
placeholder="Add a url" />
</form>
</div>
</div>
);
}
export default Search;
don't use object for booleans, just
const [loadingState, setLoadingState] = useState(false);
....
setLoadingState(true)
btw looks like a closure problem. you see loadingState always false cause the closure.
take a look at this Be Aware of Stale Closures when Using React Hooks
A way to solve it is using refs
const loadingStateRef = useRef(loadingState);
//then inside the function u can access
latestValue.current

Trying to pass parent to child in dumb component reusable component Redux

In my case I use Redux and want to make a reusable component that can pass from parent to child component when onChange function is being called. But not sure how to do that in dumb component, so I want to have an input field that when a user type some letters/numbers in that field. It should call the parent onChange function and based on that it should find the right component and pass that value to that component. Allmost any components except the CheckBox do have an onChange function. Is there a way to write that DRY and in a reusable way. There are more codes in the snippets below, please scroll down more.
const Parent = () => {
const checkForm = () => {
console.log('checkForm');
}
const onChange = (e) => {
getValue(e.target.value, e.target.name);
}
return (
<div className="App">
<form onSubmit={checkForm}>
<Name onChange={onChange}/>
<Surname onChange={onChange}/>
<Email onChange={onChange}/>
<PromoCode onChange={onChange}/>
<CheckBox/>
<button type="submit">Submit</button>
</form>
</div>
);
}
const mapDispatchToProps = dispatch => ({
getValue: (inputValue, componentName) => dispatch(getValue(inputValue, componentName))
});
const Name = () => {
return (
<div>
Name
<input
name='name'
type="text"
// value={this.state.name}
// onBlur={nameOnBlur}
onChange={onChange}
placeholder="Enter first name"
/>
</div>
)
}
const mapDispatchToProps = dispatch => ({
getValue: (inputValue, componentName) => dispatch(getValue(inputValue, componentName))
});
const mapStateToProps = (state) => {
return {
curInputValue: state.form.name || []
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Name);
export const form = (state = initialState, action) => {
switch(action.type) {
case types.GET_VALUE: {
return {
...state,
[action.componentName]: action.value
}
}
default:
return state;
}
}
To summarize the question I believe you are asking:
How do I write an onChange handler that can handle updating a variety of textfields inputs.
This could look something like:
const [formData, setFormData] = useState({name: '', surname: '', etc.})
const handleChange = (name) => event => {
setFormData({...state, [name]: event.target.value })
}
return (
<div>
<Name value={name} onChange={handleChange('name')} />
<Surname value={surname} onChange={handleChange('surname')} />
{...content}
</div> )
The key thing to note is that we store state in an object, one event handler is called for each time and it changes based on the param used.
If you are interested in using redux with this (I would advise against that unless you absolutely have to given the complexity it adds), the code would look similar but you would instead have to dispatch for each onChange fired and and the value would then be something like value={props.form.name}

Resources