Render function:
if (filteredChildren.length === 0) {
return <tbody>No projects match filter</tbody>;
}
return (
TBody({
children: filteredChildren.map(project => (
<ProjectsRow key={ project.project_id }>
{project}
</ProjectsRow>
))
})
);
This code, by the looks of it.. should never return both branches, but yet this is what I see:
And oddly enough, when I actually have some filtervalue:
To complete this nonsense, let's simply remove one line from our code.
if (filteredChildren.length === 0) {
// return <tbody>No projects match filter</tbody>; Begone!
}
return (
TBody({
children: filteredChildren.map(project => (
<ProjectsRow key={ project.project_id }>
{project}
</ProjectsRow>
))
})
);
Now everything works, no doubled up components, but no helpful message when no projects are found:
UPDATE:
A few parents up where the filterValue is state:
class FilterCard extends Component {
constructor(props: any) {
super(props);
this.state = { filterValue: `` };
}
handleInput = (): void =>
this.setState({ filterValue: this.refs.filterInput.value });
render() {
const { children } = this.props;
return (
<Row>
<input
ref="filterInput"
onChange={this.handleInput}
type="text"
/>
{ Children.map(children, child => {
return cloneElement(child, {
filterValue: this.state.filterValue
});
})}
</Row>
);
}
}
Direct parent:
export default function ProjectsTable({
sort,
order,
router,
children,
filterValue
}: ProjectsTableProps): React.Element {
return (
<Table>
<ProjectsColumns sort={ sort } order={ order } />
<ProjectsRows router={ router } filterValue={ filterValue }>
{ children }
</ProjectsRows>
</Table>
);
}
I should try filtering the children in one of the parent components.. no need to pass the filter value down just to filter it there.
Also as a crazy side note, when inspect using Chrome elements panel, that node with the text above the table.... isn't even there!!! Ghost element...!
UPDATE 2:
Filtering in the above component fixes my issue but I would LOVE to know what's causing the error the way I had it before.. I'm guessing React does some weird stuff when working inside table elements.
Working code:
export default function ProjectsTable({
sort,
order,
router,
children,
filterValue
}: ProjectsTableProps): React.Element {
const filteredChildren = children.filter(filterProjects(filterValue));
if (!filteredChildren.length) {
return <div>No projects.</div>;
}
return (
<Table>
<ProjectsColumns sort={ sort } order={ order } />
<ProjectsRows router={ router }>
{ filteredChildren }
</ProjectsRows>
</Table>
);
}
Related
My React component uses apollo to fetch data via graphql
class PopUpForm extends React.Component {
constructor () {
super()
this.state = {
shoptitle: "UpdateMe",
popupbodyDesc: "UpdateMe"
}
}
render()
{
return (
<>
<Query query={STORE_META}>
{({ data, loading, error, refetch }) => {
if (loading) return <div>Loading…</div>;
if (error) return <div>{error.message}</div>;
if (!data) return (
<p>Could not find metafields :(</p>
);
console.log(data);
//loop over data
var loopedmetafields = data.shop.metafields.edges
console.log(loopedmetafields)
loopedmetafields.forEach(element => {
console.log(element.node.value)
if (element.node.value === "ExtraShopDescription"){
this.setState({
shoptitle: element.node.value
});
console.log(this.state.shoptitle)
}
if (element.node.value === "bodyDesc"){
this.setState({
popupbodyDesc: element.node.value
});
console.log(this.state.popupbodyDesc)
}
});
return (
<>
<AddTodo mkey="ExtraShopDesc" namespace="ExtraShopDescription" desc={this.state.shoptitle} onUpdate={refetch} />
<AddTodo mkey="body" namespace="bodyDesc" desc={this.state.popupbodyDesc} onUpdate={refetch} />
</>
);
}}
</Query>
</>
)
}
}
export default PopUpForm
Frustratingly the functional component renders before the state is set from the query. Ideally the functional component would only render after this as I thought was baked into the apollo library but seems I was mistaken and it seems to execute synchronous rather than asynchronous
As you can see I pass the props to the child component, in the child component I use these to show the current value that someone might amend
The functional component is here
function AddTodo(props) {
let input;
const [desc, setDesc] = useState(props.desc);
//console.log(desc)
useEffect( () => {
console.log('props updated');
console.log(props)
}, [props.desc])
const [addTodo, { data, loading, error }] = useMutation(UPDATE_TEXT, {
refetchQueries: [
'STORE_META' // Query name
],
});
//console.log(data)
if (loading) return 'Submitting...';
if (error) return `Submission error! ${error.message}`;
return (
<div>
<form
onSubmit={e => {
console.log(input.value)
setDesc(input.value)
e.preventDefault();
const newmetafields = {
key: props.mkey,
namespace: props.namespace,
ownerId: "gid://shopify/Shop/55595073672",
type: "single_line_text_field",
value: input.value
}
addTodo({ variables: { metafields: newmetafields } });
input.value = input.value
}}
>
<p>This field denotes the title of your pop-up</p>
<input className="titleInput" defaultValue={desc}
ref={node => {
input = node;
}}
/>
<button className="buttonClick" type="submit">Update</button>
</form>
</div>
);
}
Now I need this component to update when the setState is called on PopUpForm
Another stack overflow answer here gives me some clues
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is
only called the first time the component renders. Never more. Meaning
that, if you re-render that component passing a different value as a
prop, the component will not react accordingly, because the component
will keep the state from the first time it was rendered. It's very
error prone.
Hence why I then implemented useEffect however the console.log in useEffect is still "updateMe" and not the value as returned from the graphql call.
So where I'm at
I need the render the functional component after the the grapql call
and I've manipulated the data, this seems to be the best approach in terms of design patterns also
or
I need setState to pass/render the functional component with the new value
As an aside if I do this
<AddTodo mkey="ExtraShopDesc" namespace="ExtraShopDescription" desc={data.shop.metafields.edges[0].node.value} onUpdate={refetch} />
It will work but I can't always expect the value to be 0 or 1 as metafields might have already defined
I think there is a simpler way than using setState to solve this. You can for example use find like this:
const shopTitleElement = loopedmetafields.find(element => {
return element.node.value === "ExtraShopDescription"
})
const shopBodyElement = loopedmetafields.find(element => {
return element.node.value === "bodyDesc"
});
return (
<>
<AddTodo mkey="ExtraShopDesc" namespace="ExtraShopDescription" desc={shopTitleElement.node.value} onUpdate={refetch} />
<AddTodo mkey="body" namespace="bodyDesc" desc={shopBodyElement.node.value} onUpdate={refetch} />
</>
);
I am attempting to filter a list of conversations by participant names. The participant names are properties inside of a participant list and the participant list is contained within a list of conversations.
So far, I have approached the problem by attempting to nest filters:
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.filter(
(participant) => {
return participant.fallback_name.toLowerCase().indexOf(
this.state.searchTerm.toLowerCase()) !== -1;
})
})
This appears to work, insofar as I can confirm (i.e. I put a whole bunch of console.logs throughout an expanded version of the above) that as the searchTerm state is updated, it returns matching the participant and the matching convo. However, filteredConvos is not correctly rendered to reflect the newly filtered array.
I am new to Javascript, React, and Stack Overflow. My best assessment is that I am incorrectly passing my filtered array items back to filteredConvos, but I honestly don't have a enough experience to know.
Any assistance is deeply appreciated.
Further context:
The data source I'm working with is a JSON file provided by
google of an account's Hangouts chat.
HangoutSearch.js:
class HangoutSearch extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
searchTerm: e.target.value
});
}
render() {
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.filter(
(participant) => {
return participant.fallback_name.toLowerCase().indexOf(
this.state.searchTerm.toLowerCase()) !== -1;
})
})
return(
<div>
<Form>
<Form.Control
placeholder='Enter the name of the chat participant'
value={this.state.searchTerm}
onChange={this.handleChange} />
</Form>
<HangoutList filteredConvos={filteredConvos}/>
</div>
);
}
}
export default HangoutSearch;
HangoutList.js
class HangoutList extends Component {
render() {
return(
<ListGroup>
{this.props.filteredConvos.map((convo) => {
return (
<ListGroup.Item key={convo.conversation.conversation.id.id}>
{convo.conversation.conversation.participant_data.map(
(participant) => {
return (
<span key={participant.id.gaia_id}>
{participant.fallback_name}
</span>
)
}
)}
</ListGroup.Item>
)
})}
</ListGroup>
);
}
}
export default HangoutList;
The inner .filter always returns an array, which are truthy in Javascript. You could use .some instead:
let filteredConvos = this.props.convos.filter((convo) => {
return convo.conversation.conversation.participant_data.some((participant) => {
return participant.fallback_name.toLowerCase().indexOf( this.state.searchTerm.toLowerCase()) !== -1;
})
})
A button click shall filter my job-card array to only one category. E.g. button "Marketing" should filter to those jobs from array who have prop "jobstags: Marketing". I used a very similar procedure like for my input which filters jobs perfectly.
I can console log my event (the button click) with the according value ("Marketing"). But it still doesn't filter correctly...
In my app I did this:
export default class App extends Component {
state = {
jobs: jobs,
searchfield: '',
jobtags: ''
}
onSearchChange = event => {
this.setState({ searchfield: event.target.value })
}
onClickChange = event => {
console.log(event.target.value)
this.setState({ jobtags: event.target.value })
}
render() {
const filteredJobs = this.state.jobs.filter(job => {
return (
job.position
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.company
.toLowerCase()
.includes(this.state.searchfield.toLowerCase()) ||
job.jobtags.toLowerCase().includes(this.state.jobtags.toLowerCase())
)
})
// this.save()
if (this.state.jobs.length === 0) {
return <Loading>Loading...</Loading>
} else {
return (
<Router>
<React.Fragment>
<Route
exact
path="/"
render={() => (
<Home
jobs={filteredJobs}
searchChange={this.onSearchChange}
clickChange={this.onClickChange}
/>
)}
/>
onClickChange is what should update the state of tags
In my Home component I then simply pass the value on to the Categories component:
<Categories clickChange={clickChange} />
Finally it arrives in my Categories component where I say:
export default class Categories extends Component {
render() {
const { clickChange } = this.props
return (
<Wrapper>
<button value="Marketing" onClick={clickChange}>
<img
alt="Button"
src={require('/Users/markus/Documents/q4-2018/jobs-app/src/img/computer.png')}
/>
Frontend
</button> ...
Any ideas? Thx!
maybe you have to bind the "this" of "onClickChange", for example in the constructor of your App class.
Example :
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
jobs: jobs,
searchfield: '',
jobtags: ''
};
this.onClickChange = this.onClickChange.bind(this);
and it will work I think
You will have to bind it. Add this line to your constructor:
this.onClickChange = this.onClickChange.bind(this);
Im making my first react project. Im new in JS, HTML, CSS and even web app programming.
What i want to do it is a Search input label. Now its look like this:
Like you can see i have some list of objects and text input.
I Have two components, my ProjectList.js with Search.js component...
class ProjectsList extends Component {
render() {
return (
<div>
<Search projects={this.props.projects} />
<ListGroup>
{this.props.projects.map(project => {
return <Project project={project} key={project.id} />;
})}
</ListGroup>
</div>
);
}
}
export default ProjectsList;
... and ProjectList.js displays Project.js:
How looks Search.js (its not ended component)
class Search extends Component {
state = {
query: ""
};
handleInputChange = () => {
this.setState({
query: this.search.value
});
};
render() {
return (
<form>
<input
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
<p />
</form>
);
}
}
export default Search;
My project have name property. Could you tell me how to code Search.js component poperly, to change displaying projects dynamically based on input in text label? for example, return Project only, if text from input match (i want to search it dynamically, when i start typing m... it shows all projects started on m etc).
How to make that Search input properly? How to make it to be universal, for example to Search in another list of objects? And how to get input from Search back to Parent component?
For now, in react dev tools whatever i type there i get length: 0
Thanks for any advices!
EDIT:
If needed, my Project.js component:
class Project extends Component {
state = {
showDetails: false
};
constructor(props) {
super(props);
this.state = {
showDetails: false
};
}
toggleShowProjects = () => {
this.setState(prevState => ({
showDetails: !prevState.showDetails
}));
};
render() {
return (
<ButtonToolbar>
<ListGroupItem className="spread">
{this.props.project.name}
</ListGroupItem>
<Button onClick={this.toggleShowProjects} bsStyle="primary">
Details
</Button>
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
</ButtonToolbar>
);
}
}
export default Project;
To create a "generic" search box, perhaps you could do something like the following:
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
This revised version of Search takes some additional props which allows it to be reused as required. In addition to the projects prop, you also pass filterProject and onUpdateProjects callbacks which are provided by calling code. The filterProject callback allows you to provide custom filtering logic for each <Search/> component rendered. The onUpdateProjects callback basically returns the "filtered list" of projects, suitable for rendering in the parent component (ie <ProjectList/>).
The only other significant change here is the addition of visibleProjects to the state of <ProjectList/> which tracks the visible (ie filtered) projects from the original list of projects passed to <ProjectList/>:
class Project extends React.Component {
render() {
return (
<div>{ this.props.project }</div>
);
}
}
class ProjectsList extends React.Component {
componentWillMount() {
this.setState({ visibleProjects : [] })
}
render() {
return (
<div>
<Search projects={this.props.projects} filterProject={ (query,project) => (project == query) } onUpdateProjects={ projects => this.setState({ visibleProjects : projects }) } />
<div>
{this.state.visibleProjects.map(project => {
return <Project project={project} key={project.id} />;
})}
</div>
</div>
);
}
}
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
ReactDOM.render(
<ProjectsList projects={[0,1,2,3]} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I will assumes both your Search and ProjectList component have a common parent that contains the list of your projects.
If so, you should pass a function into your Search component props, your Search component will then call this function when the user typed something in the search bar. This will help your parent element decide what your ProjectsLists needs to render :
handleInputChange = () => {
this.props.userSearchInput(this.search.value);
this.setState({
query: this.search.value
});
};
And now, here is what the parent element needs to include :
searchChanged = searchString => {
const filteredProjects = this.state.projects.filter(project => project.name.includes(searchString))
this.setState({ filteredProjects })
}
With this function, you will filter out the projects that includes the string the user typed in their names, you will then only need to put this array in your state and pass it to your ProjectsList component props
You can find the documentation of the String includes function here
You can now add this function to the props of your Search component when creating it :
<Search userSearchInput={searchChanged}/>
And pass the filtered array into your ProjectsList props :
<ProjectsList projects={this.state.filteredProjects}/>
Side note : Try to avoid using refs, the onCHnage function will send an "event" object to your function, containing everything about what the user typed :
handleInputChange = event => {
const { value } = event.target
this.props.userSearchInput(value);
this.setState({
query: value
});
};
You can now remove the ref from your code
I have a SearchBar component and it has a subcomponent SearchBarItem.
I passed the method handleSelectItem() to subcomponent to dispatch value to store and it works (I saw it from the Redux tool in Chrome).
Then, when I tried to get the value from the method submitSearch(), which I also passed it from the parent component, it shows:
Cannot read property 'area' of undefined.
I'm still not so familiar with React. If someone can help, it will be very appreciated. Thanks in advance.
This is parent component SearchBar:
class SearchBar extends Component {
handleSelectItem = (selectCategory, selectedItem) => {
if (selectCategory === 'areas') {
this.props.searchActions.setSearchArea(selectedItem);
}
}
submitSearch() {
console.log(this.props.area); // this one is undefined
}
render() {
return (
<div className="searchBar">
<SearchBarItem
selectCategory="areas"
name="地區"
options={this.props.areaOptions}
handleSelectItem={this.handleSelectItem}
submitSearch={this.submitSearch}
/>
</div>
);
}
}
const mapStateToProps = state => ({
area: state.search.area,
brandOptions: state.search.brandOptions,
vehicleTypeOptions: state.search.vehicleTypeOptions,
areaOptions: state.search.areaOptions,
});
const mapDispatchToProps = dispatch => ({
searchActions: bindActionCreators(searchActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
This is subcomponent SearchBarItem:
export default class SearchBarItem extends Component {
state = {
showOptions: false,
selectedItem: [],
}
handleSelectItem = (selectedItem) => this.props.handleSelectItem(this.props.selectCategory, selectedItem);
submitSearch = () => this.props.submitSearch();
handleClickCategory = () => {
this.setState({ showOptions: !this.state.showOptions });
}
handleClickItem(option) {
this.setState({
selectedItem: [...this.state.selectedItem, option],
}, () => this.handleSelectItem(this.state.selectedItem));
}
render() {
const options = this.props.options.map((item, index) => (
<div
className={this.state.selectedItem === item ? "searchBarItem__option--active" : "searchBarItem__option"}
key={index}
onClick={() => this.handleClickItem(item)}
>
{item}
</div>
));
const optionBox = (
<div className="searchBarItem__box">
<div
className="searchBarItem__option"
onClick={() => this.handleClickItem('')}
>
不限{this.props.name}
</div>
{options}
<div className="searchBarItem__confirm">
<span>取消</span><span onClick={() => this.submitSearch()} >套用</span>
</div>
</div>
);
return (
<div className="searchBarItem">
<span onClick={() => this.handleClickCategory()} >
{(() => {
switch (this.state.selectedItem.length) {
case 0: return this.props.name;
case 1: return this.state.selectedItem[0];
default: return `${this.state.selectedItem.length} ${this.props.name}`;
}
})()}
</span>
{ this.state.selectedItem.length > 0 ? '' : <Icon icon={ICONS.DROP_DOWN} size={18} /> }
{ this.state.showOptions ? optionBox : '' }
</div>
);
}
}
SearchBarItem.propTypes = {
name: PropTypes.string.isRequired,
selectCategory: PropTypes.string.isRequired,
options: PropTypes.arrayOf(PropTypes.string).isRequired,
handleSelectItem: PropTypes.func.isRequired,
submitSearch: PropTypes.func.isRequired,
};
Your problem caused by the behavior of this pointer in javascript.
By writing the code submitSearch={this.submitSearch} you are actually sending a pointer to the submitSearch method but losing the this pointer.
The method actually defers as MyClass.prototype.myMethod. By sending a pointer to the method MyClass.prototype.myMethod you are not specifying to what instance of MyClass it belongs to (if at all). This is not the most accurate explanation of how this pointer works but it's intuitive explanation, you can read more here about how this pointer works
You have some possible options to solve it:
Option one (typescript/babel transpiler only) - define method as class variable
class MyClass{
myMethod = () => {
console.log(this instanceof MyClass) // true
}
}
this will automatically do option 2 for you
Option two - Bind the method on the constructor
class MyClass{
constructor(){
this.myMethod = this.myMethod.bind(this)
}
myMethod() {
console.log(this instanceof MyClass) // true
}
}
By the second way, you are binding the method to current this instance
Small note, you should avoid doing:
<MyComponent onSomeCallback={this.myCallback.bind(this)} />
Function.prototype.bind returns a new method and not mutating the existing one, so each render you'll create a new method and it has performance impact on render (binding it on the constructor only once as option two, is fine)